/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "uv.h"
#include "uv-common.h"

#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stddef.h> /* NULL */
#include <stdio.h>
#include <stdlib.h> /* malloc */
#include <string.h> /* memset */

#if defined(_WIN32)
#include <malloc.h> /* malloc */
#else
#include <net/if.h> /* if_nametoindex */
#endif

typedef struct {
    uv_malloc_func local_malloc;
    uv_realloc_func local_realloc;
    uv_calloc_func local_calloc;
    uv_free_func local_free;
} uv__allocator_t;

static uv__allocator_t uv__allocator = {
    malloc,
    realloc,
    calloc,
    free,
};

char* uv__strdup(const char* s)
{
    size_t len = strlen(s) + 1;
    char* m = uv__malloc(len);
    if (m == NULL)
        return NULL;
    return memcpy(m, s, len);
}

char* uv__strndup(const char* s, size_t n)
{
    char* m;
    size_t len = strlen(s);
    if (n < len)
        len = n;
    m = uv__malloc(len + 1);
    if (m == NULL)
        return NULL;
    m[len] = '\0';
    return memcpy(m, s, len);
}

void* uv__malloc(size_t size)
{
    return uv__allocator.local_malloc(size);
}

void uv__free(void* ptr)
{
    int saved_errno;

    /* Libuv expects that free() does not clobber errno.  The system allocator
   * honors that assumption but custom allocators may not be so careful.
   */
    saved_errno = errno;
    uv__allocator.local_free(ptr);
    errno = saved_errno;
}

void* uv__calloc(size_t count, size_t size)
{
    return uv__allocator.local_calloc(count, size);
}

void* uv__realloc(void* ptr, size_t size)
{
    return uv__allocator.local_realloc(ptr, size);
}

int uv_replace_allocator(uv_malloc_func malloc_func,
    uv_realloc_func realloc_func,
    uv_calloc_func calloc_func,
    uv_free_func free_func)
{
    if (malloc_func == NULL || realloc_func == NULL || calloc_func == NULL || free_func == NULL) {
        return UV_EINVAL;
    }

    uv__allocator.local_malloc = malloc_func;
    uv__allocator.local_realloc = realloc_func;
    uv__allocator.local_calloc = calloc_func;
    uv__allocator.local_free = free_func;

    return 0;
}

#define XX(uc, lc) \
    case UV_##uc:  \
        return sizeof(uv_##lc##_t);

size_t uv_handle_size(uv_handle_type type)
{
    switch (type) {
        UV_HANDLE_TYPE_MAP(XX)
    default:
        return -1;
    }
}

size_t uv_req_size(uv_req_type type)
{
    switch (type) {
        UV_REQ_TYPE_MAP(XX)
    default:
        return -1;
    }
}

#undef XX

size_t uv_loop_size(void)
{
    return sizeof(uv_loop_t);
}

uv_buf_t uv_buf_init(char* base, unsigned int len)
{
    uv_buf_t buf;
    buf.base = base;
    buf.len = len;
    return buf;
}

static const char* uv__unknown_err_code(int err)
{
    char buf[32];
    char* copy;

    snprintf(buf, sizeof(buf), "Unknown system error %d", err);
    copy = uv__strdup(buf);

    return copy != NULL ? copy : "Unknown system error";
}

#define UV_ERR_NAME_GEN(name, _) \
    case UV_##name:              \
        return #name;
const char* uv_err_name(int err)
{
    switch (err) {
        UV_ERRNO_MAP(UV_ERR_NAME_GEN)
    }
    return uv__unknown_err_code(err);
}
#undef UV_ERR_NAME_GEN

#define UV_STRERROR_GEN(name, msg) \
    case UV_##name:                \
        return msg;
const char* uv_strerror(int err)
{
    switch (err) {
        UV_ERRNO_MAP(UV_STRERROR_GEN)
    }
    return uv__unknown_err_code(err);
}
#undef UV_STRERROR_GEN

int uv_ip4_addr(const char* ip, int port, struct sockaddr_in* addr)
{
    memset(addr, 0, sizeof(*addr));
    addr->sin_family = AF_INET;
    addr->sin_port = htons(port);
    return uv_inet_pton(AF_INET, ip, &(addr->sin_addr.s_addr));
}

int uv_ip6_addr(const char* ip, int port, struct sockaddr_in6* addr)
{
    char address_part[40];
    size_t address_part_size;
    const char* zone_index;

    memset(addr, 0, sizeof(*addr));
    addr->sin6_family = AF_INET6;
    addr->sin6_port = htons(port);

    zone_index = strchr(ip, '%');
    if (zone_index != NULL) {
        address_part_size = zone_index - ip;
        if (address_part_size >= sizeof(address_part))
            address_part_size = sizeof(address_part) - 1;

        memcpy(address_part, ip, address_part_size);
        address_part[address_part_size] = '\0';
        ip = address_part;

        zone_index++; /* skip '%' */
        /* NOTE: unknown interface (id=0) is silently ignored */
#ifdef _WIN32
        addr->sin6_scope_id = atoi(zone_index);
#else
        addr->sin6_scope_id = if_nametoindex(zone_index);
#endif
    }

    return uv_inet_pton(AF_INET6, ip, &addr->sin6_addr);
}

int uv_ip4_name(const struct sockaddr_in* src, char* dst, size_t size)
{
    return uv_inet_ntop(AF_INET, &src->sin_addr, dst, size);
}

int uv_ip6_name(const struct sockaddr_in6* src, char* dst, size_t size)
{
    return uv_inet_ntop(AF_INET6, &src->sin6_addr, dst, size);
}

int uv_tcp_bind(uv_tcp_t* handle,
    const struct sockaddr* addr,
    unsigned int flags)
{
    unsigned int addrlen;

    if (handle->type != UV_TCP)
        return UV_EINVAL;

    if (addr->sa_family == AF_INET)
        addrlen = sizeof(struct sockaddr_in);
    else if (addr->sa_family == AF_INET6)
        addrlen = sizeof(struct sockaddr_in6);
    else
        return UV_EINVAL;

    return uv__tcp_bind(handle, addr, addrlen, flags);
}

int uv_udp_bind(uv_udp_t* handle,
    const struct sockaddr* addr,
    unsigned int flags)
{
    unsigned int addrlen;

    if (handle->type != UV_UDP)
        return UV_EINVAL;

    if (addr->sa_family == AF_INET)
        addrlen = sizeof(struct sockaddr_in);
    else if (addr->sa_family == AF_INET6)
        addrlen = sizeof(struct sockaddr_in6);
    else
        return UV_EINVAL;

    return uv__udp_bind(handle, addr, addrlen, flags);
}

int uv_tcp_connect(uv_connect_t* req,
    uv_tcp_t* handle,
    const struct sockaddr* addr,
    uv_connect_cb cb)
{
    unsigned int addrlen;

    if (handle->type != UV_TCP)
        return UV_EINVAL;

    if (addr->sa_family == AF_INET)
        addrlen = sizeof(struct sockaddr_in);
    else if (addr->sa_family == AF_INET6)
        addrlen = sizeof(struct sockaddr_in6);
    else
        return UV_EINVAL;

    return uv__tcp_connect(req, handle, addr, addrlen, cb);
}

int uv_udp_send(uv_udp_send_t* req,
    uv_udp_t* handle,
    const uv_buf_t bufs[],
    unsigned int nbufs,
    const struct sockaddr* addr,
    uv_udp_send_cb send_cb)
{
    unsigned int addrlen;

    if (handle->type != UV_UDP)
        return UV_EINVAL;

    if (addr->sa_family == AF_INET)
        addrlen = sizeof(struct sockaddr_in);
    else if (addr->sa_family == AF_INET6)
        addrlen = sizeof(struct sockaddr_in6);
    else
        return UV_EINVAL;

    return uv__udp_send(req, handle, bufs, nbufs, addr, addrlen, send_cb);
}

int uv_udp_try_send(uv_udp_t* handle,
    const uv_buf_t bufs[],
    unsigned int nbufs,
    const struct sockaddr* addr)
{
    unsigned int addrlen;

    if (handle->type != UV_UDP)
        return UV_EINVAL;

    if (addr->sa_family == AF_INET)
        addrlen = sizeof(struct sockaddr_in);
    else if (addr->sa_family == AF_INET6)
        addrlen = sizeof(struct sockaddr_in6);
    else
        return UV_EINVAL;

    return uv__udp_try_send(handle, bufs, nbufs, addr, addrlen);
}

int uv_udp_recv_start(uv_udp_t* handle,
    uv_alloc_cb alloc_cb,
    uv_udp_recv_cb recv_cb)
{
    if (handle->type != UV_UDP || alloc_cb == NULL || recv_cb == NULL)
        return UV_EINVAL;
    else
        return uv__udp_recv_start(handle, alloc_cb, recv_cb);
}

int uv_udp_recv_stop(uv_udp_t* handle)
{
    if (handle->type != UV_UDP)
        return UV_EINVAL;
    else
        return uv__udp_recv_stop(handle);
}

void uv_walk(uv_loop_t* loop, uv_walk_cb walk_cb, void* arg)
{
    QUEUE queue;
    QUEUE* q;
    uv_handle_t* h;

    QUEUE_MOVE(&loop->handle_queue, &queue);
    while (!QUEUE_EMPTY(&queue)) {
        q = QUEUE_HEAD(&queue);
        h = QUEUE_DATA(q, uv_handle_t, handle_queue);

        QUEUE_REMOVE(q);
        QUEUE_INSERT_TAIL(&loop->handle_queue, q);

        if (h->flags & UV__HANDLE_INTERNAL)
            continue;
        walk_cb(h, arg);
    }
}

static void uv__print_handles(uv_loop_t* loop, int only_active, FILE* stream)
{
    const char* type;
    QUEUE* q;
    uv_handle_t* h;

    if (loop == NULL)
        loop = uv_default_loop();

    QUEUE_FOREACH(q, &loop->handle_queue)
    {
        h = QUEUE_DATA(q, uv_handle_t, handle_queue);

        if (only_active && !uv__is_active(h))
            continue;

        switch (h->type) {
#define X(uc, lc)   \
    case UV_##uc:   \
        type = #lc; \
        break;
            UV_HANDLE_TYPE_MAP(X)
#undef X
        default:
            type = "<unknown>";
        }

        fprintf(stream,
            "[%c%c%c] %-8s %p\n",
            "R-"[!(h->flags & UV__HANDLE_REF)],
            "A-"[!(h->flags & UV__HANDLE_ACTIVE)],
            "I-"[!(h->flags & UV__HANDLE_INTERNAL)],
            type,
            (void*)h);
    }
}

void uv_print_all_handles(uv_loop_t* loop, FILE* stream)
{
    uv__print_handles(loop, 0, stream);
}

void uv_print_active_handles(uv_loop_t* loop, FILE* stream)
{
    uv__print_handles(loop, 1, stream);
}

void uv_ref(uv_handle_t* handle)
{
    uv__handle_ref(handle);
}

void uv_unref(uv_handle_t* handle)
{
    uv__handle_unref(handle);
}

int uv_has_ref(const uv_handle_t* handle)
{
    return uv__has_ref(handle);
}

void uv_stop(uv_loop_t* loop)
{
    loop->stop_flag = 1;
}

uint64_t uv_now(const uv_loop_t* loop)
{
    return loop->time;
}

size_t uv__count_bufs(const uv_buf_t bufs[], unsigned int nbufs)
{
    unsigned int i;
    size_t bytes;

    bytes = 0;
    for (i = 0; i < nbufs; i++)
        bytes += (size_t)bufs[i].len;

    return bytes;
}

int uv_recv_buffer_size(uv_handle_t* handle, int* value)
{
    return uv__socket_sockopt(handle, SO_RCVBUF, value);
}

int uv_send_buffer_size(uv_handle_t* handle, int* value)
{
    return uv__socket_sockopt(handle, SO_SNDBUF, value);
}

int uv_fs_event_getpath(uv_fs_event_t* handle, char* buffer, size_t* size)
{
    size_t required_len;

    if (!uv__is_active(handle)) {
        *size = 0;
        return UV_EINVAL;
    }

    required_len = strlen(handle->path);
    if (required_len >= *size) {
        *size = required_len + 1;
        return UV_ENOBUFS;
    }

    memcpy(buffer, handle->path, required_len);
    *size = required_len;
    buffer[required_len] = '\0';

    return 0;
}

/* The windows implementation does not have the same structure layout as
 * the unix implementation (nbufs is not directly inside req but is
 * contained in a nested union/struct) so this function locates it.
*/
static unsigned int* uv__get_nbufs(uv_fs_t* req)
{
#ifdef _WIN32
    return &req->fs.info.nbufs;
#else
    return &req->nbufs;
#endif
}

/* uv_fs_scandir() uses the system allocator to allocate memory on non-Windows
 * systems. So, the memory should be released using free(). On Windows,
 * uv__malloc() is used, so use uv__free() to free memory.
*/
#ifdef _WIN32
#define uv__fs_scandir_free uv__free
#else
#define uv__fs_scandir_free free
#endif

void uv__fs_scandir_cleanup(uv_fs_t* req)
{
    uv__dirent_t** dents;

    unsigned int* nbufs = uv__get_nbufs(req);

    dents = req->ptr;
    if (*nbufs > 0 && *nbufs != (unsigned int)req->result)
        (*nbufs)--;
    for (; *nbufs < (unsigned int)req->result; (*nbufs)++)
        uv__fs_scandir_free(dents[*nbufs]);

    uv__fs_scandir_free(req->ptr);
    req->ptr = NULL;
}

int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent)
{
    uv__dirent_t** dents;
    uv__dirent_t* dent;

    unsigned int* nbufs = uv__get_nbufs(req);

    dents = req->ptr;

    /* Free previous entity */
    if (*nbufs > 0)
        uv__fs_scandir_free(dents[*nbufs - 1]);

    /* End was already reached */
    if (*nbufs == (unsigned int)req->result) {
        uv__fs_scandir_free(dents);
        req->ptr = NULL;
        return UV_EOF;
    }

    dent = dents[(*nbufs)++];

    ent->name = dent->d_name;
#ifdef HAVE_DIRENT_TYPES
    switch (dent->d_type) {
    case UV__DT_DIR:
        ent->type = UV_DIRENT_DIR;
        break;
    case UV__DT_FILE:
        ent->type = UV_DIRENT_FILE;
        break;
    case UV__DT_LINK:
        ent->type = UV_DIRENT_LINK;
        break;
    case UV__DT_FIFO:
        ent->type = UV_DIRENT_FIFO;
        break;
    case UV__DT_SOCKET:
        ent->type = UV_DIRENT_SOCKET;
        break;
    case UV__DT_CHAR:
        ent->type = UV_DIRENT_CHAR;
        break;
    case UV__DT_BLOCK:
        ent->type = UV_DIRENT_BLOCK;
        break;
    default:
        ent->type = UV_DIRENT_UNKNOWN;
    }
#else
    ent->type = UV_DIRENT_UNKNOWN;
#endif

    return 0;
}

int uv_loop_configure(uv_loop_t* loop, uv_loop_option option, ...)
{
    va_list ap;
    int err;

    va_start(ap, option);
    /* Any platform-agnostic options should be handled here. */
    err = uv__loop_configure(loop, option, ap);
    va_end(ap);

    return err;
}

static uv_loop_t default_loop_struct;
static uv_loop_t* default_loop_ptr;

uv_loop_t* uv_default_loop(void)
{
    if (default_loop_ptr != NULL)
        return default_loop_ptr;

    if (uv_loop_init(&default_loop_struct))
        return NULL;

    default_loop_ptr = &default_loop_struct;
    return default_loop_ptr;
}

uv_loop_t* uv_loop_new(void)
{
    uv_loop_t* loop;

    loop = uv__malloc(sizeof(*loop));
    if (loop == NULL)
        return NULL;

    if (uv_loop_init(loop)) {
        uv__free(loop);
        return NULL;
    }

    return loop;
}

int uv_loop_close(uv_loop_t* loop)
{
    QUEUE* q;
    uv_handle_t* h;

    if (!QUEUE_EMPTY(&(loop)->active_reqs))
        return UV_EBUSY;

    QUEUE_FOREACH(q, &loop->handle_queue)
    {
        h = QUEUE_DATA(q, uv_handle_t, handle_queue);
        if (!(h->flags & UV__HANDLE_INTERNAL))
            return UV_EBUSY;
    }

    uv__loop_close(loop);

#ifndef NDEBUG
    memset(loop, -1, sizeof(*loop));
#endif
    if (loop == default_loop_ptr)
        default_loop_ptr = NULL;

    return 0;
}

void uv_loop_delete(uv_loop_t* loop)
{
    uv_loop_t* default_loop;
    int err;

    default_loop = default_loop_ptr;

    err = uv_loop_close(loop);
    (void)err; /* Squelch compiler warnings. */
    assert(err == 0);
    if (loop != default_loop)
        uv__free(loop);
}
